-
Notifications
You must be signed in to change notification settings - Fork 18
記事の追加: 「単純なHaskellのみでServant並に高機能なライブラリーを作ろうとした振り返り」 #223
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
| headingDivClass: post-heading | ||
| author: YAMAMOTO Yuji | ||
| postedBy: <a href="http://the.igreque.info/">YAMAMOTO Yuji(@igrep)</a> | ||
| date: July 27, 2025 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TODO: 日付を後で直す。
|
|
||
| 手が遅いもので、私が最初にwai-sampleのリポジトリーに対して行った[最初のコミット](https://github.com/igrep/wai-sample/commit/37f49dfe86af7482b09ab82b2282c5b9bf1cd73d)から、既に約5年の歳月が過ぎました[^last-commit]。当時は私の前職、IIJにおける社内勉強会のネタとして始めたのが懐かしいです。私が知る限り、当時はwai-sampleのように「値レベルのプログラミングで」「Servantのように1つの定義からクライアントやドキュメントの生成も出来る」ことを目指したライブラリーはなかったように思います。しかし実際のところ、執筆時点で次のライブラリーが類似の機能を実装しているようです。これらのライブラリーがいつ開発を始めたのかは分かりませんが、やはり私がwai-sampleを作り始めた時点で同じような問題意識を持った人はいたのでしょう。 | ||
|
|
||
| [^last-commit]: [実装に対する最後の修正](https://github.com/igrep/wai-sample/commit/b2647de2a1a4c7ec8c799ec07972c3d9df6fcb55)からも既に約1年が過ぎました。記録を作るのも遅い...😥 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| [^last-commit]: [実装に対する最後の修正](https://github.com/igrep/wai-sample/commit/b2647de2a1a4c7ec8c799ec07972c3d9df6fcb55)からも既に約1年が過ぎました。記録を作るのも遅い...😥 | |
| [^last-commit]: [実装に対する最後の修正](https://github.com/igrep/wai-sample/commit/b2647de2a1a4c7ec8c799ec07972c3d9df6fcb55)からも既に1年以上が過ぎました。記録を作るのも遅い...😥 |
igrep
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
一通り読めた。来週直す。
| get @(PlainText, T.Text) "aboutUs" (path "about/us") (\_ -> return "About IIJ") | ||
| ``` | ||
|
|
||
| 先程のサンプルコードから抜粋した最も単純な例↑では、`get`関数を使ってエンドポイントを定義しています。`get`関数は名前のとおりHTTPのGETメソッドに対応するエンドポイントを定義します。`TypeApplications`言語拡張を使って指定している`(PlainText, T.Text)`という型が、このエンドポイントが返すレスポンスの型を表しています。ここでは、`get`に渡す最後の引数に当たる関数([`Responder`](https://github.com/igrep/wai-sample/blob/b4ddb75a28b927b76ac7c4c182bad6812769ed01/src/WaiSample/Types.hs#L104)と呼びます。詳細は後ほど)がレスポンスボディーとして返す型をお馴染みの`Text`型として指定しつつ、サーバーやクライアントが処理する際はMIMEタイプを`text/plain`として扱うように指定しています。 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
特に詳細に説明しているわけではないので
| 先程のサンプルコードから抜粋した最も単純な例↑では、`get`関数を使ってエンドポイントを定義しています。`get`関数は名前のとおりHTTPのGETメソッドに対応するエンドポイントを定義します。`TypeApplications`言語拡張を使って指定している`(PlainText, T.Text)`という型が、このエンドポイントが返すレスポンスの型を表しています。ここでは、`get`に渡す最後の引数に当たる関数([`Responder`](https://github.com/igrep/wai-sample/blob/b4ddb75a28b927b76ac7c4c182bad6812769ed01/src/WaiSample/Types.hs#L104)と呼びます。詳細は後ほど)がレスポンスボディーとして返す型をお馴染みの`Text`型として指定しつつ、サーバーやクライアントが処理する際はMIMEタイプを`text/plain`として扱うように指定しています。 | |
| 先程のサンプルコードから抜粋した最も単純な例↑では、`get`関数を使ってエンドポイントを定義しています。`get`関数は名前のとおりHTTPのGETメソッドに対応するエンドポイントを定義します。`TypeApplications`言語拡張を使って指定している`(PlainText, T.Text)`という型が、このエンドポイントが返すレスポンスの型を表しています。ここでは、`get`に渡す最後の引数に当たる関数([`Responder`](https://github.com/igrep/wai-sample/blob/b4ddb75a28b927b76ac7c4c182bad6812769ed01/src/WaiSample/Types.hs#L104)と呼びます。後述します)がレスポンスボディーとして返す型をお馴染みの`Text`型として指定しつつ、サーバーやクライアントが処理する際はMIMEタイプを`text/plain`として扱うように指定しています。 |
| ) | ||
| ``` | ||
|
|
||
| 実際のところここまでの話は`Route`型の値をサーバーアプリケーションが解釈した場合の挙動です。`Route`型はパスの仕様を定義する`Applicative`な内部DSLとなっています。これによって、サーバーアプリケーションだけでなくクライアントのコード生成機能やドキュメントの生成など、様々な応用ができるようになっています。詳しくは後述しますが、例えばクライアントのコード生成機能が`Route`型の値を解釈すると、`decimalPiece`や`paramPiece`などの値は生成した関数の引数を一つずつ追加します。 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
「一つ」や「二つ」などは私は最近は漢数字ではなく算用数字にしています。他と一貫させやすいからです。Software Designに寄稿したときにそう訂正されるうちに気付きました。
%s/一つ/1つ/g|
|
||
| ℹ️[こちら](https://github.com/igrep/wai-sample/blob/b4ddb75a28b927b76ac7c4c182bad6812769ed01/src/WaiSample/Client/Sample.hs)からほぼそのままコピペしたコードです。 | ||
|
|
||
| 上記の通り、クライアントコードの生成は`TemplateHaskell`を使って行います。`declareClient`という関数に、生成する関数の名前の接頭辞(prefix)とこれまで定義した`Handler`型のリスト(`sampleRoutes`)を渡すと、次のような型の関数の定義を生成します[^ddump-splices]: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
しまった。TemplateHaskellを使うことについて釈明を書くのを忘れていた。
|
|
||
| [`decimalPiece`](https://github.com/igrep/wai-sample/blob/b4ddb75a28b927b76ac7c4c182bad6812769ed01/src/WaiSample/Routes.hs#L22)は`Route Integer`という型で、それに`show <$>`を適用した結果は`Route String`となります。`Route String`は、パスの一部として文字列を受け取ることを表す型ですから、上記の式は`integers/<任意の文字列>`というパスを表すことになります。ところが!実際にサーバーアプリケーションがパスをパースするのに使っているのは`decimalPiece`なので、整数でなければなりません。このように`<$>`を使うだけで、`Route String`という型が表すパスのパーサーと、実際にパースできるパスの仕様が食い違ってしまうことがあります。`Applicative`(厳密に言えば`Functor`の機能ですが)を使ったDSLである以上、こうしたことが防げないのです。 | ||
|
|
||
| まあ、実は同じ問題が同じように`Applicative`ベースの内部DSLを使った他のライブラリーにもあるでしょうから、敢えて気にしない、という手もあるのかも知れませんが。ちなみに、似たような問題を解決するため[relational-record](https://hackage.haskell.org/package/relational-record)というパッケージでは`Functor`や`Applicative`は使わず、[product-isomorphic](https://hackage.haskell.org/package/product-isomorphic)というパッケージにおいて、言わば「コンストラクターだけが適用できる`Functor`・`Applicative`」とも言うべき専用の型クラスを作ることで解決していました。wai-sampleもこれを使えないかと企みましたが、どうもうまく適用できなかったため諦めました。 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| まあ、実は同じ問題が同じように`Applicative`ベースの内部DSLを使った他のライブラリーにもあるでしょうから、敢えて気にしない、という手もあるのかも知れませんが。ちなみに、似たような問題を解決するため[relational-record](https://hackage.haskell.org/package/relational-record)というパッケージでは`Functor`や`Applicative`は使わず、[product-isomorphic](https://hackage.haskell.org/package/product-isomorphic)というパッケージにおいて、言わば「コンストラクターだけが適用できる`Functor`・`Applicative`」とも言うべき専用の型クラスを作ることで解決していました。wai-sampleもこれを使えないかと企みましたが、どうもうまく適用できなかったため諦めました。 | |
| まあ、実は同じ問題が同じように`Applicative`ベースの内部DSLを使った他のライブラリーにもあるでしょうから、敢えて気にしない、という手もあるのかも知れませんが。ちなみに、似たような問題を解決するため[relational-record](https://hackage.haskell.org/package/relational-record)というパッケージでは`Functor`や`Applicative`は使わず、[product-isomorphic](https://hackage.haskell.org/package/product-isomorphic)というパッケージで、言わば「コンストラクターだけが適用できる`Functor`・`Applicative`」とも言うべき専用の型クラスを作ることで解決していました。wai-sampleもこれを使えないかと企みましたが、どうもうまく適用できなかったため諦めました。 |
No description provided.